﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using LinqToLfsWorld.Collections;

namespace LinqToLfsWorld.Linq
{

	/// <summary>
	/// Takes an array of property names and tries to find them in the expression,
	/// as a set of binary expressions.
	/// </summary>
	/// <remarks>All the resulting values are placed inside the public 'OutputValues' collection.</remarks>
	internal class PropertyExpressionFinder : Linq.ExpressionVisitor
	{
		//public object Value { get; private set; }
		public string [] PropertyNames { get; private set; }
		public ExpressionValueCollection OutputValues { get; private set; }

		public PropertyExpressionFinder()
		{
			OutputValues = new ExpressionValueCollection();
		}

		/// <summary>
		/// Finds the expression values given an initial expression, and some property names to find.
		/// </summary>
		/// <param name="expression">The expression.</param>
		/// <param name="propertyNames">The property names.</param>
		/// <returns>A collection of found property names and their values.</returns>
		public ExpressionValueCollection FindExpressionValues(Expression expression, string[] propertyNames)
		{
			PropertyNames = propertyNames;
			this.Visit(expression);
			return OutputValues;
		}

		/// <summary>
		/// Visits the binary expression. Only "equals" expression types are supported.
		/// </summary>
		/// <param name="b">The binary expression to process.</param>
		/// <exception cref="NotSupportedException">The expression type was something other than "equals".</exception>
		protected override System.Linq.Expressions.Expression VisitBinary(System.Linq.Expressions.BinaryExpression b)
		{
			if (b.NodeType == ExpressionType.AndAlso)
			{
				Visit(b.Left);
				Visit(b.Right);
			}
			else if (b.NodeType == System.Linq.Expressions.ExpressionType.Equal)
			{
				if (b.Left is UnaryExpression || b.Right is UnaryExpression)
				{
					UnaryExpression unary = (b.Left is UnaryExpression) ?
						(UnaryExpression)b.Left :
						(UnaryExpression)b.Right;

					Expression valueExpression = (b.Left is UnaryExpression) ? b.Right : b.Left;

					Expression result = Visit(unary.Operand);

					if (result is MemberExpression)
					{
						HandleMemberAccess((MemberExpression)result, valueExpression);
					}
				}
				else
				{
					if (b.Left is MemberExpression || b.Right is MemberExpression)
					{
						MemberExpression member = (b.Left is MemberExpression) ?
							(MemberExpression)b.Left : (MemberExpression)b.Right;

						Expression valueExpression = (b.Left is MemberExpression) ? b.Right : b.Left;

						HandleMemberAccess(member, valueExpression);
					}
				}
			}
			else
			{
				// For this implementation, any queries other than 'equals' are not supported, since they're not
				// supported by the server. If any such queries are required, the query should be enumerated
				// and Linq To Objects used instead.

				throw new NotSupportedException("Query type " + b.NodeType + " is not supported.");
			}

			return b;
		}


		/// <summary>
		/// Gets the actual value from memberExpression and adds it to the output list.
		/// </summary>
		/// <remarks>Here, a check is made to make sure it exists inside the list of properties we're actually looking for
		/// (PropertyNames). If it doesn't exist, a NotSupportedException is thrown.</remarks>
		/// <param name="memberExpression">The member expression.</param>
		/// <param name="valueExpression">The value expression.</param>
		/// <exception cref="NotSupportedException">The property being evaluated is not present in the 
		/// PropertyNames collection.</exception>
		protected void HandleMemberAccess(MemberExpression memberExpression, Expression valueExpression)
		{
			if (PropertyNames.Contains(memberExpression.Member.Name))
			{
				if (valueExpression.NodeType == ExpressionType.Constant)
				{
					OutputValues.Add(memberExpression.Member.Name,
						new ExpressionValue() { Value = ((ConstantExpression)valueExpression).Value });
				}
			}
			else
			{
				throw new NotSupportedException(memberExpression.Member.Name + " is not queryable");
			}
		}

	}
}
